{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c34b879b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# LangSmith\n",
    "import getpass\n",
    "import os\n",
    "\n",
    "os.environ[\"LANGSMITH_TRACING\"] = \"true\"\n",
    "os.environ[\"LANGSMITH_API_KEY\"] = getpass.getpass()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "92d391ef",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Select chat model\n",
    "import getpass\n",
    "import os\n",
    "\n",
    "if not os.environ.get(\"DEEPSEEK_API_KEY\"):\n",
    "  os.environ[\"DEEPSEEK_API_KEY\"] = getpass.getpass(\"Enter API key for DeepSeek: \")\n",
    "\n",
    "from langchain.chat_models import init_chat_model\n",
    "\n",
    "llm = init_chat_model(\"deepseek-chat\", model_provider=\"deepseek\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "71eed827",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Select embeddings model:\n",
    "from langchain_ollama import OllamaEmbeddings\n",
    "\n",
    "embeddings = OllamaEmbeddings(model=\"bge-m3:latest\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f68a005f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Select vector store\n",
    "from langchain_postgres import PGVector\n",
    "\n",
    "vector_store = PGVector(\n",
    "    embeddings=embeddings,\n",
    "    collection_name=\"my_docs\",\n",
    "    connection=\"postgresql+psycopg://docker:docker@localhost:5432/postgres\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "97162171",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "USER_AGENT environment variable not set, consider setting it to identify your requests.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total characters: 43047\n"
     ]
    }
   ],
   "source": [
    "# Loading documents\n",
    "import bs4\n",
    "from langchain_community.document_loaders import WebBaseLoader\n",
    "\n",
    "# Only keep post title, headers, and content from the full HTML.\n",
    "bs4_strainer = bs4.SoupStrainer(class_=(\"post-title\", \"post-header\", \"post-content\"))\n",
    "loader = WebBaseLoader(\n",
    "    web_paths=(\"https://lilianweng.github.io/posts/2023-06-23-agent/\",),\n",
    "    bs_kwargs={\"parse_only\": bs4_strainer},\n",
    ")\n",
    "docs = loader.load()\n",
    "\n",
    "assert len(docs) == 1\n",
    "print(f\"Total characters: {len(docs[0].page_content)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "e10331a2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "      LLM Powered Autonomous Agents\n",
      "    \n",
      "Date: June 23, 2023  |  Estimated Reading Time: 31 min  |  Author: Lilian Weng\n",
      "\n",
      "\n",
      "Building agents with LLM (large language model) as its core controller is a cool concept. Several proof-of-concepts demos, such as AutoGPT, GPT-Engineer and BabyAGI, serve as inspiring examples. The potentiality of LLM extends beyond generating well-written copies, stories, essays and programs; it can be framed as a powerful general problem solver.\n",
      "Agent System Overview#\n",
      "In\n"
     ]
    }
   ],
   "source": [
    "print(docs[0].page_content[:500])\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af807fa6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Splitting documents"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "86fd2814",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Split blog post into 63 sub-documents.\n"
     ]
    }
   ],
   "source": [
    "from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
    "\n",
    "text_splitter = RecursiveCharacterTextSplitter(\n",
    "    chunk_size=1000,  # chunk size (characters)\n",
    "    chunk_overlap=200,  # chunk overlap (characters)\n",
    "    add_start_index=True,  # track index in original document\n",
    ")\n",
    "all_splits = text_splitter.split_documents(docs)\n",
    "\n",
    "print(f\"Split blog post into {len(all_splits)} sub-documents.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "2a6f1cfe",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['c9ee033f-0a6c-4839-8855-23216a4dbf99', '03a54e89-f074-4c92-826b-37e762cd1e50', '359d1e54-ecb0-4a4c-ba0c-e9d465dde665']\n"
     ]
    }
   ],
   "source": [
    "# Storing documents\n",
    "document_ids = vector_store.add_documents(documents=all_splits)\n",
    "\n",
    "print(document_ids[:3])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "0d67d2cf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.\n",
      "Question: (question goes here) \n",
      "Context: (context goes here) \n",
      "Answer:\n"
     ]
    }
   ],
   "source": [
    "# Retrieval and Generation\n",
    "from langchain import hub\n",
    "\n",
    "# N.B. for non-US LangSmith endpoints, you may need to specify\n",
    "# api_url=\"https://api.smith.langchain.com\" in hub.pull.\n",
    "prompt = hub.pull(\"rlm/rag-prompt\")\n",
    "\n",
    "example_messages = prompt.invoke(\n",
    "    {\"context\": \"(context goes here)\", \"question\": \"(question goes here)\"}\n",
    ").to_messages()\n",
    "\n",
    "assert len(example_messages) == 1\n",
    "print(example_messages[0].content)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "cde9a727",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.documents import Document\n",
    "from typing_extensions import List, TypedDict\n",
    "\n",
    "\n",
    "class State(TypedDict):\n",
    "    question: str\n",
    "    context: List[Document]\n",
    "    answer: str\n",
    "\n",
    "def retrieve(state: State):\n",
    "    retrieved_docs = vector_store.similarity_search(state[\"question\"])\n",
    "    return {\"context\": retrieved_docs}\n",
    "\n",
    "\n",
    "def generate(state: State):\n",
    "    docs_content = \"\\n\\n\".join(doc.page_content for doc in state[\"context\"])\n",
    "    messages = prompt.invoke({\"question\": state[\"question\"], \"context\": docs_content})\n",
    "    response = llm.invoke(messages)\n",
    "    return {\"answer\": response.content}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "5445ce05",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.graph import START, StateGraph\n",
    "\n",
    "graph_builder = StateGraph(State).add_sequence([retrieve, generate])\n",
    "graph_builder.add_edge(START, \"retrieve\")\n",
    "graph = graph_builder.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "c4ebf998",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Context: [Document(id='359d1e54-ecb0-4a4c-ba0c-e9d465dde665', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/', 'start_index': 1638}, page_content='Component One: Planning#\\nA complicated task usually involves many steps. An agent needs to know what they are and plan ahead.\\nTask Decomposition#\\nChain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process.\\nTree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote.'), Document(id='1413f4d7-1de3-4631-aa23-d615dd2a19a3', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/', 'start_index': 2578}, page_content='Task decomposition can be done (1) by LLM with simple prompting like \"Steps for XYZ.\\\\n1.\", \"What are the subgoals for achieving XYZ?\", (2) by using task-specific instructions; e.g. \"Write a story outline.\" for writing a novel, or (3) with human inputs.\\nAnother quite distinct approach, LLM+P (Liu et al. 2023), involves relying on an external classical planner to do long-horizon planning. This approach utilizes the Planning Domain Definition Language (PDDL) as an intermediate interface to describe the planning problem. In this process, LLM (1) translates the problem into “Problem PDDL”, then (2) requests a classical planner to generate a PDDL plan based on an existing “Domain PDDL”, and finally (3) translates the PDDL plan back into natural language. Essentially, the planning step is outsourced to an external tool, assuming the availability of domain-specific PDDL and a suitable planner which is common in certain robotic setups but not in many other domains.\\nSelf-Reflection#'), Document(id='750a85ed-27a4-4990-9d1f-26e6ed26cef6', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/', 'start_index': 4312}, page_content='Examples of reasoning trajectories for knowledge-intensive tasks (e.g. HotpotQA, FEVER) and decision-making tasks (e.g. AlfWorld Env, WebShop). (Image source: Yao et al. 2023).\\n\\nIn both experiments on knowledge-intensive tasks and decision-making tasks, ReAct works better than the Act-only baseline where Thought: … step is removed.\\nReflexion (Shinn & Labash 2023) is a framework to equip agents with dynamic memory and self-reflection capabilities to improve reasoning skills. Reflexion has a standard RL setup, in which the reward model provides a simple binary reward and the action space follows the setup in ReAct where the task-specific action space is augmented with language to enable complex reasoning steps. After each action $a_t$, the agent computes a heuristic $h_t$ and optionally may decide to reset the environment to start a new trial depending on the self-reflection results.\\n\\n\\nIllustration of the Reflexion framework. (Image source: Shinn & Labash, 2023)'), Document(id='e3841797-8015-4c53-bce1-d8f6f3d3a6f0', metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/', 'start_index': 3549}, page_content='Self-Reflection#\\nSelf-reflection is a vital aspect that allows autonomous agents to improve iteratively by refining past action decisions and correcting previous mistakes. It plays a crucial role in real-world tasks where trial and error are inevitable.\\nReAct (Yao et al. 2023) integrates reasoning and acting within LLM by extending the action space to be a combination of task-specific discrete actions and the language space. The former enables LLM to interact with the environment (e.g. use Wikipedia search API), while the latter prompting LLM to generate reasoning traces in natural language.\\nThe ReAct prompt template incorporates explicit steps for LLM to think, roughly formatted as:\\nThought: ...\\nAction: ...\\nObservation: ...\\n... (Repeated many times)')]\n",
      "\n",
      "\n",
      "Answer: Task decomposition is a technique used to break down complex tasks into smaller, more manageable steps. It can be achieved through methods like Chain of Thought prompting, which instructs a model to \"think step by step,\" or by using task-specific instructions and human input. Another approach, LLM+P, outsources this planning to an external classical planner.\n"
     ]
    }
   ],
   "source": [
    "result = graph.invoke({\"question\": \"What is Task Decomposition?\"})\n",
    "\n",
    "print(f\"Context: {result['context']}\\n\\n\")\n",
    "print(f\"Answer: {result['answer']}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5aca7c40",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b1d6c5b4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
